/*
 * ============================================================================
 *
 *       Filename:  serial.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2018年04月16日 14时18分43秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  jianxi sun (jianxi), ycsunjane@gmail.com
 *   Organization:  
 *
 * ============================================================================
 */
#include "config.h"
#include <stdio.h>
#include <stdint.h>
#include <termios.h>  
#include <fcntl.h>  
#include <arpa/inet.h>

#include "list.h"
#include "common.h"
#include "arg.h"
#include "serial.h"

extern char *s_popen(char *data, char *para);

static int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop,
	uint8_t vmin, uint8_t vtime)  
{ 
	struct termios newtio,oldtio;  
	if  ( tcgetattr( fd,&oldtio)  !=  0) {   
		sys_err("SetupSerial 1");  
		return -1;  
	}  
	bzero( &newtio, sizeof( newtio ) );  
	/* canon */
	newtio.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
		|INLCR|IGNCR|ICRNL|IXON);
	newtio.c_oflag &= ~OPOST;
	newtio.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
	newtio.c_cflag &= ~(CSIZE|PARENB);
	newtio.c_cflag |= CS8;

	newtio.c_cflag  |=  CLOCAL | CREAD;  
	newtio.c_cflag &= ~CSIZE;  

	switch( nBits )  
	{  
	case 5:
		newtio.c_cflag |= CS5;  
		break;  
	case 6:
		newtio.c_cflag |= CS6;  
		break;  
	case 7:  
		newtio.c_cflag |= CS7;  
		break;  
	case 8:  
		newtio.c_cflag |= CS8;  
		break;  
	}  

	switch( nEvent )  
	{  
	case 'O':  
		newtio.c_cflag |= PARENB;  
		newtio.c_cflag |= PARODD;  
		newtio.c_iflag |= (INPCK | ISTRIP);  
		break;  
	case 'E':   
		newtio.c_iflag |= (INPCK | ISTRIP);  
		newtio.c_cflag |= PARENB;  
		newtio.c_cflag &= ~PARODD;  
		break;  
	case 'N':    
		newtio.c_cflag &= ~PARENB;  
		break;  
	default:
		sys_err("error parity\n");
		exit(-1);
	}  

	switch( nSpeed )  
	{  
	case 2400:  
		cfsetispeed(&newtio, B2400);  
		cfsetospeed(&newtio, B2400);  
		break;  
	case 4800:  
		cfsetispeed(&newtio, B4800);  
		cfsetospeed(&newtio, B4800);  
		break;  
	case 9600:  
		cfsetispeed(&newtio, B9600);  
		cfsetospeed(&newtio, B9600);  
		break;  
	case 19200:  
		cfsetispeed(&newtio, B19200);  
		cfsetospeed(&newtio, B19200);  
		break;  
	case 38400:
		cfsetispeed(&newtio, B38400);  
		cfsetospeed(&newtio, B38400);  
		break;  
	case 57600:
		cfsetispeed(&newtio, B57600);  
		cfsetospeed(&newtio, B57600);  
		break;  
	case 115200:  
		cfsetispeed(&newtio, B115200);  
		cfsetospeed(&newtio, B115200);  
		break;  
	case 230400:  
		cfsetispeed(&newtio, B230400);  
		cfsetospeed(&newtio, B230400);  
		break;  
	case 460800:  
		cfsetispeed(&newtio, B460800);  
		cfsetospeed(&newtio, B460800);  
		break;  
	default:  
		sys_err("error bps\n");
		exit(-1);
	}  
	if( nStop == 1 )  
		newtio.c_cflag &=  ~CSTOPB;  
	else if ( nStop == 2 )  
		newtio.c_cflag |=  CSTOPB;  
	newtio.c_cc[VTIME]  = vtime;
	newtio.c_cc[VMIN] = vmin;
	tcflush(fd,TCIFLUSH);  
	if((tcsetattr(fd,TCSANOW,&newtio))!=0)  
	{  
		sys_err("com set error");  
		return -1;  
	}  
	return 0;  
}

static void free_serial(serial_t *serial)
{
	sys_err("serial error, close and free serial\n");
	ss_free(serial->recv_ctx);
	ss_free(serial);
}

void close_and_free_serial(EV_P_ serial_t *serial)
{
	if (serial != NULL) {
		sys_err("serial error, free serial fd: %d\n", serial->fd);
		ev_io_stop(EV_A_ & serial->recv_ctx->io);
		close(serial->fd);
		free_serial(serial);
	}
}

void serial_recv_cb(EV_P_ ev_io *w, int revents)
{
	serial_ctx_t *serial_recv_ctx   = container_of(w, serial_ctx_t, io);
	serial_t *serial		= serial_recv_ctx->serial;

	ssize_t r;
	char buffer[MSS] = {0};

	r = read(serial->fd, buffer, sizeof(buffer));
	if (r == 0) {
		sys_debug("serial recv 0, return\n");
		return;
	} else if (r == -1) {
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			sys_debug("recv again\n");
			return;
		} else {
			sys_err("recv failed: %s(%d)\n",
				strerror(errno), errno);
			close_and_free_serial(EV_A_ serial);
			sys_err("ttl: %s error, exit\n", args_info.dev_arg);
			exit(-1);
		}
	} else {
		char data[MSS] = {0};
		if(strstr(buffer, "ifname") || strstr(buffer, "lte")) {
			if (s_popen(data, "module"))
				write(serial->fd, data, strlen(data));
		} else if(strstr(buffer, "gnss") || strstr(buffer, "gps")) {
			if (s_popen(data, NULL))
				write(serial->fd, data, strlen(data));
		}
	}
}

static serial_t *new_serial(int fd)
{
	serial_t *serial;
	serial = ss_malloc(sizeof(serial_t));

	serial->fd = fd;
	serial->recv_ctx		= ss_malloc(sizeof(serial_ctx_t));
	serial->recv_ctx->serial	= serial;

	ev_io_init(&serial->recv_ctx->io, serial_recv_cb, fd, EV_READ);

	return serial;
}

serial_t *create_serial()
{
	int fd, ret;
	fd = open(args_info.dev_arg, O_RDWR | O_NOCTTY);
	if(fd < 0) {
		fprintf(stderr, "open %s failed:%s\n", 
			args_info.dev_arg, 
			strerror(errno));
		exit(-1);
	}

	ret = set_opt(fd , args_info.speed_arg, args_info.data_arg, 
		args_info.parity_arg[0], args_info.stop_arg,
		args_info.vmin_arg, args_info.vtime_arg);
	if(ret < 0) {
		fprintf(stderr, "set tty failed:%s\n", 
			strerror(errno));
		exit(-1);
	}
	serial_t * serial = new_serial(fd);

	return serial;
}
